package db

import (
	"context"
	"database/sql"
	"errors"
	"time"

	jet "github.com/go-jet/jet/v2/mysql"
	"github.com/samber/lo"
)

// Exists checks for existence of the rows in a table with optional condition.
func Exists(ctx context.Context, db *sql.DB, from jet.ReadableTable, condition jet.BoolExpression) (bool, error) {
	if db == nil {
		return false, errors.New("db is nil")
	}

	var result struct {
		Exists bool `db:"exists"`
	}

	stmt := jet.SELECT(jet.Int(1)).FROM(from)

	if condition != nil {
		stmt = stmt.WHERE(condition)
	}

	if err := jet.SELECT(jet.EXISTS(stmt).AS("exists")).QueryContext(ctx, db, &result); err != nil {
		return false, err
	}

	return result.Exists, nil
}

// Count returns row count from a table with optional condition.
func CountExpression(ctx context.Context, db *sql.DB, count jet.Expression, from jet.ReadableTable, condition jet.BoolExpression) (int64, error) {
	if db == nil {
		return -1, errors.New("db is nil")
	}

	var result struct {
		Count int64 `db:"count"`
	}

	if count == nil {
		count = jet.Int(1)
	}

	stmt := jet.SELECT(jet.COUNT(count).AS("count")).FROM(from)

	if condition != nil {
		stmt = stmt.WHERE(condition)
	}

	if err := stmt.QueryContext(ctx, db, &result); err != nil {
		return -1, err
	}

	return result.Count, nil
}

// Count returns row count from a table with optional condition.
func Count(ctx context.Context, db *sql.DB, from jet.ReadableTable, condition jet.BoolExpression) (int64, error) {
	return CountExpression(ctx, db, nil, from, condition)
}

// SliceExpression returns a slice of jet.Expression from a slice of JetExpressionType, by now it is string and time.
func SliceExpression[T JetExpressionType](slice []T) []jet.Expression {
	return lo.Map(slice, func(item T, _ int) jet.Expression {
		switch v := any(item).(type) {
		case string:
			return jet.String(v)
		case int:
			return jet.Int((int64(v)))
		case time.Time:
			return jet.TimeT(v)
		default:
			return nil
		}
	})
}

func TimestampsExpr(dates []time.Time) []jet.Expression {
	return lo.Map(dates, func(item time.Time, _ int) jet.Expression {
		return jet.TimestampT(item)
	})
}

type JetExpressionType interface {
	~string | ~int | time.Time
}
